
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.quantum.jbencoder.parser;

//~--- non-JDK imports --------------------------------------------------------
import com.quantum.jbencoder.types.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

//~--- JDK imports ------------------------------------------------------------

import java.io.InputStream;
import java.io.OutputStream;

/**
 *
 * @author g073025
 */
public class Parser {

    private final byte DICTIONNARY_PREFIX = 'd';
    private final byte LIST_PREFIX = 'l';
    private final byte INTEGER_PREFIX = 'i';
    private final byte SUFFIX = 'e';
    private final byte SEPARATOR = ':';

    //private final byte STRING_PREFIX = 'd';
    /**
     * default Parser constructor
     */
    public Parser() {
    }

    /**
     * deserialize bencoded UTF 8 byte array input stream into bencodedType data
     * structure
     *
     * @param bencodedInputStream
     * @return
     */
    public BencodedType parse(InputStream bencodedInputStream) throws IOException {
        BencodedDictionnary root = new BencodedDictionnary();
        BencodedType data = null;
        //TODO: deserialize method
        bencodedInputStream.mark(10);
        int character = bencodedInputStream.read();
        int r;
        while (character != -1) {
            switch ((byte) character) {
                case (SUFFIX):
                    return data;
                case (DICTIONNARY_PREFIX):
                    data = new BencodedDictionnary();
                    bencodedInputStream.mark(10);
                    r = bencodedInputStream.read();
                    while ((byte) r != SUFFIX && (byte) r != -1) {
                        bencodedInputStream.reset();
                        BencodedType key = parse(bencodedInputStream);
                        if (key != null && key instanceof BencodedString) {
                             System.out.println("adding key " + ((BencodedString) key).getValue());

                            BencodedType value = parse(bencodedInputStream);
                            if (value != null) {
                               
                                ((BencodedDictionnary) data).put((BencodedString) key, value);
                                bencodedInputStream.mark(10);
                                r = bencodedInputStream.read();
                            } else {
                                throw new IOException("Dictionnary analysis: unexpected null value!");
                            }

                        } else {
                            throw new IOException("Dictionnary analysis: key is null or not a bencodedString!");
                        }
                    }
                    bencodedInputStream.reset();
                    break;
                case (LIST_PREFIX):
                    data = new BencodedList();
                    bencodedInputStream.mark(10);
                    r = bencodedInputStream.read();
                    if ((byte) r == SUFFIX) {
                        return data;
                    }


                    do {
                        bencodedInputStream.reset();

                        BencodedType element = parse(bencodedInputStream);

                        if (element != null) {
                            ((BencodedList) data).add(element);
                        }
                        bencodedInputStream.mark(10);
                        r = bencodedInputStream.read();

                    } while (((byte) r != SUFFIX) && ((byte) r != -1));
                    bencodedInputStream.reset();
                    break;
                case (INTEGER_PREFIX):
                    data = new BencodedInteger();
                    ((BencodedInteger) data).setValue(parseInt(SUFFIX, bencodedInputStream));
                    return data;
                default:
                    //probably a string
                    //length:value
                    bencodedInputStream.reset();
                    String szVal = parseString(bencodedInputStream);
                    data = new BencodedString();
                    ((BencodedString) data).setValue(szVal);
                    return data;
            }
            bencodedInputStream.mark(10);
            character = bencodedInputStream.read();
        }
        return data;
    }

    /**
     * serialize bencodeType data structure into UTF 8 byte Array output stream
     *
     * @param bencodedTypeData
     * @return
     */
    public void serialize(BencodedType bencodedTypeData, OutputStream os) throws IOException {
        bencodedTypeData.serialize(os);
    }

    /**
     * return string read
     *
     * @param bencodedInputStream
     * @return
     * @throws IOException
     */
    private String parseString(InputStream bencodedInputStream) throws IOException {
        int length = (int) parseInt(SEPARATOR, bencodedInputStream);
        byte[] szBytes = new byte[length];

        int nbRead = bencodedInputStream.read(szBytes);

        String szString = new String(szBytes, "UTF-8");
        System.out.println(szString);
        return (szString);

    }

    /**
     * read bencodeInputStream until byte 'c' is read and try to convert string
     * read as long value for example parseInt('e', "92e") return 92l
     *
     * @param c
     * @param bencodedInputStream
     * @return
     * @throws IOException
     */
    private long parseInt(final byte endToken, InputStream bencodedInputStream) throws IOException {
        String szVal = "";
        int character = bencodedInputStream.read();
        while (character != endToken) {
            //System.out.print("" + (character - '0'));
            if (character == -1) {
                throw new IOException("unexpected end of stream!");
            }
            szVal = szVal.concat("" + (character - '0'));
            character = bencodedInputStream.read();
        }
        
        System.out.println("(long) "+szVal);
        return Long.parseLong(szVal);

    }
}
